package client

import (
	"acs/comet/proto"
	"acs/pbmodel"
	"fmt"

	"net/http"

	_ "bytes"
	"io/ioutil"
	"strings"

	pb "github.com/golang/protobuf/proto"
)

type HandleFunc func(req *proto.Request, client *Client) (pb.Message, error)

func HandlePing(_ *proto.Request, _ *Client) (pb.Message, error) {
	pr := &pbmodel.PingResp{
		Resp: pb.String("OK"),
	}
	return pr, nil
}

func HandleDefault(req *proto.Request, _ *Client) (pb.Message, error) {
	pr := &pbmodel.ProtoErr{
		Msg:  pb.String(fmt.Sprintf("unknown cmd: %v", req.Cmd)),
		Code: pb.Int(proto.CmdErrCodeUnknownCmd),
	}
	return pr, nil
}
func HandleEvent(req *proto.Request, _ *Client) (pb.Message, error) {
	re := &pbmodel.EventResp{}
	re.Status = pb.Int(proto.CmdStatusOk)
	return re, nil
}

func HandleRegister(req *proto.Request, client *Client) (pb.Message, error) {
	registerInfo := &pbmodel.RegisterInfo{}
	err := pb.Unmarshal(req.Data, registerInfo)
	re := &pbmodel.RegisterInfoResp{}
	if err != nil {
		re.Msg = pb.String("Invalid register data: " + err.Error())
		re.Status = pb.Int(proto.CmdErrCodeDataDecodeErr)
		return re, nil
	}

	client.Registe(registerInfo)
	re.Msg = pb.String("OK")
	re.Status = pb.Int(proto.CmdHandledSuccess)
	return re, nil
}

func HandlePatchFin(req *proto.Request, client *Client) (pb.Message, error) {
	finReq := &pbmodel.Patchfin{}
	re := &pbmodel.PatchfinResp{}
	err := pb.Unmarshal(req.Data, finReq)
	if err != nil {
		re.Msg = pb.String("Invalid patchfin data: " + err.Error())
		re.Status = pb.Int(3001)
		return re, nil
	}
	regInfo := client.GetRegisterInfo()
	// TODO: validate LPID
	regInfo.LPID = finReq.LPID
	client.SetRegisterInfo(regInfo)
	re.Status = pb.Int(proto.CmdStatusOk)
	return re, nil
}

func HandleSchemaFin(req *proto.Request, client *Client) (pb.Message, error) {
	finReq := &pbmodel.Schemafin{}
	re := &pbmodel.SchemafinResp{}
	err := pb.Unmarshal(req.Data, finReq)
	if err != nil {
		re.Msg = pb.String("Invalid schemafin data: " + err.Error())
		re.Status = pb.Int(4001)
	}

	regInfo := client.GetRegisterInfo()
	// TODO: validate LPID
	regInfo.LSID = finReq.LSID
	client.SetRegisterInfo(regInfo)
	re.Status = pb.Int(proto.CmdStatusOk)
	return re, nil

}

func HandleForward(req *proto.Request, _ *Client) (result pb.Message, e error) {
	re := &pbmodel.ForwardHttpResp{}
	var forwardReq pbmodel.ForwardHttp
	defer func() {
		loggerHandle.Debugw("forward",
			"data", req.ToString(),
			"result", result,
			"err", e)
	}()
	err := pb.Unmarshal(req.Data, &forwardReq)
	if err != nil {
		re.Msg = &pbmodel.HttpResponse{}
		re.Msg.Body = pb.String("Invalid forward http data" + err.Error())
		re.Status = pb.Int(8001)
		return re, err
	}

	status, resp := forwardRequest(&forwardReq)
	re.Status = pb.Int(status)
	re.Msg = resp
	return re, nil
}

func forwardRequest(forwardReq *pbmodel.ForwardHttp) (int, *pbmodel.HttpResponse) {
	httpreq, err := newHttpRequest(forwardReq)
	httpresp := &pbmodel.HttpResponse{}
	if err != nil {
		httpresp.Body = pb.String(err.Error())
		return 8001, httpresp
	}
	resp, err := http.DefaultClient.Do(httpreq)
	if err != nil {
		httpresp.Body = pb.String(err.Error())
		return 8002, httpresp
	}
	defer resp.Body.Close()
	retData, err := readResp(resp)
	if err != nil {
		httpresp.Body = pb.String(err.Error())
		return 8002, httpresp
	}
	httpresp.StatusCode = pb.Int(resp.StatusCode)
	httpresp.Body = pb.String(retData)

	if resp.StatusCode != http.StatusOK {
		return 8003, httpresp
	}
	return proto.CmdStatusOk, httpresp
}

func newHttpRequest(forwardReq *pbmodel.ForwardHttp) (*http.Request, error) {
	httpreq, err := http.NewRequest(strings.ToUpper(forwardReq.GetMethod()), forwardReq.GetUrl(), strings.NewReader(string(forwardReq.GetBody())))

	if err != nil {
		return nil, err
	}
	if forwardReq.Header != nil {
		for _, item := range forwardReq.Header.Items {
			httpreq.Header.Add(item.GetKey(), item.GetValue())
		}
	}
	return httpreq, nil
}

func readResp(resp *http.Response) (string, error) {
	ret, err := ioutil.ReadAll(resp.Body)
	return string(ret), err
}
